package com.app.baselibrary.http.flow
import android.accounts.NetworkErrorException
import com.app.baselibrary.http.HttpResultException
import com.app.baselibrary.http.ResultBean
import com.app.baselibrary.ktx.gson
import com.app.baselibrary.ktx.jsonConvertList
import com.app.baselibrary.ktx.shortToast
import com.app.baselibrary.ktx.toJson
import com.app.baselibrary.utils.NetworkUtil
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import okhttp3.Call
import okhttp3.Response
import java.nio.charset.StandardCharsets

/**
 * http flow
 */
class HttpFlow(private val call: Call,
               private val exceptionConvert: ExceptionConvert<Throwable>? = null) {


    private var _start:()->Unit = {}

    private var _complete:()->Unit = {}

    private var mError:Throwable.()->Unit = {}

    private var observerOn: CoroutineScope = CoroutineScope(SupervisorJob()
            + Dispatchers.Default)

    private fun getConvertError(exception: Throwable): Throwable {
        return exceptionConvert?.convert(exception)?: throw IllegalStateException("请先配置 ExceptionConvert")
    }


    fun onStart(start: ()->Unit) = apply {
        this._start = start
    }

    fun onComplete(complete: ()->Unit)= apply {
        this._complete = complete
    }

    @Suppress("UNCHECKED_CAST")
    fun  onException(error: HttpResultException.()->Unit) = apply {
        this.mError = error as Throwable.() -> Unit
    }

    fun toastExceptionMessage() = apply {
        onException {
//            LogUtil.logE((this as HttpResultException).code)
            shortToast(message)
        }
    }

    @Suppress("UNCHECKED_CAST")
    fun onError(error: Throwable.()->Unit) = apply {
        this.mError = error
    }


    fun <D> collectMain(flow: HttpFlow.()->Flow<D>, result: (D)->Unit){
        collect(Dispatchers.Main.immediate, flow, result)
    }

    fun <D> collect(flow: HttpFlow.()->Flow<D>, result: (D)->Unit){
        collect(Dispatchers.Default, flow, result)
    }
    /**
     * 获取结果
     * @param dispatcher [Dispatchers.Main] [Dispatchers.IO] [Dispatchers.Default] ...
     */
    fun <D>collect(dispatcher: CoroutineDispatcher, flow: HttpFlow.()->Flow<D>, result: (D)->Unit){
        this.observerOn = CoroutineScope(SupervisorJob() + dispatcher)
        this.observerOn.launch {
            flow.invoke(this@HttpFlow).collect { result.invoke(it) }
        }
    }


    inline fun <reified D: Any> asDataEntity() = asDataEntity(D::class.java)

    inline fun <reified D: Any> asDataList() = asDataList(D::class.java)


    fun <D: Any> asDataList(clazz: Class<D>) = asResponseBean().mapNotNull {
        try {
            if (it.isSuccessful()&&it.data!=null&&it.data.toString().isNotEmpty()){
                it.getResultData().toJson.jsonConvertList(clazz)
            }else{
                throw HttpResultDataError(it)
            }
        }catch (e: Exception){
            throw e
        }
    }.catch { mError.invoke(getConvertError(it)) }


    fun <D: Any> asDataEntity(clazz: Class<D>) = asResponseBean().mapNotNull {
        try {
            if (it.isSuccessful()&&it.data!=null&&it.data.toString().isNotEmpty()){
                gson.fromJson(it.getResultData().toJson, clazz)
            }else {
                throw HttpResultDataError(it)
            }
        }catch (e: Exception){
            throw e
        }
    }.catch {eit->
        mError.invoke(getConvertError(eit))
    }


    fun asResponseBean() = asString()
        .mapNotNull {
            @Suppress("UNCHECKED_CAST")
            val responseBeanClazz = ResultBean::class.java
            gson.fromJson(it, responseBeanClazz)
        }
        .catch { mError.invoke(getConvertError(it)) }


    fun asString() = asInputStream()
        .mapNotNull {
            String(it.readBytes(), StandardCharsets.UTF_8)
        }
        .catch { mError.invoke(getConvertError(it)) }


    fun asInputStream() = asResponse().mapNotNull {
        if (it.isSuccessful && it.body != null){
            it.body!!.byteStream()
        }else{
            throw HttpResponseException(it)
        }
    }.catch {
        val e = getConvertError(it)
        mError.invoke(e)
    }
  fun asResponse() = flow {
        val response = getHttpCallResult()
        emit(response)
    }.flowOn(Dispatchers.IO)
        .onStart { _start.invoke() }
        .catch { mError.invoke(getConvertError(it)) }
        .onCompletion { _complete.invoke() }

    private fun getHttpCallResult(): Response {
        if (!NetworkUtil.active()){
            throw NetworkErrorException()
        }
        return call.execute()
    }
    /**
     * 异常转换
     */
    interface ExceptionConvert<E: Throwable>{
        fun convert(exception: Throwable): E
    }
    /**
     * 网络请求数据解析结果错误异常
     */
    data class HttpResultDataError(val responseBean: ResultBean): Throwable()


    class HttpResponseException(val response: Response): Throwable()

}